// 严格类型检查
function _checkType(typeStr, data) {
  return Object.prototype.toString.call(data) === typeStr;
}
// 柯里化后的函数
var _curryCheckType = curry(_checkType);
var isString = _curryCheckType('[object String]'); // 检查字符串
var isArray = _curryCheckType('[object Array]'); // 检查数组
var isFunction = _curryCheckType('[object Function]'); // 检查函数
var isObject = _curryCheckType('[object Object]'); // 检查对象
var isNumber = _curryCheckType('[object Number]'); // 检查数字
var isBoolean = _curryCheckType('[object Boolean]'); // 检查布尔值

function throttle(fn, wait, immediate) {
  if (!isFunction(fn)) {
    throw new Error('参数 fn 必须是函数类型');
  }
  if (!isNumber(wait)) {
    throw new Error('参数 wait 必须是数字类型');
  }
  if (immediate !== undefined && !isBoolean(immediate)) {
    throw new Error('参数 immediate 必须是布尔类型');
  }

  var timer;
  var previous = immediate ? 0 : +new Date();

  function callFunc(context, args) {
    fn.apply(context, args);
    previous = +new Date();
    timer = null;
  }

  return function () {
    var args = arguments;
    var now = +new Date();
    // 如果immediate是false，那剩余时间则是wait(第一次触发事件时)
    // 如果immediate是true，那剩余时间则是wait-now < 0
    var remaining = wait - (now - previous);

    if (remaining <= 0) {
      // 如果remianing <= 0，则说明间隔时间大于了wait秒，则立即执行1次事件
      if (timer) {
        clearTimeout(timer);
      }

      callFunc(this, args);
    } else if (!timer) {
      // 如果还有剩余时间，且没有定时器，则设置个定时器到时间执行
      timer = setTimeout(() => {
        callFunc(this, args);
      }, remaining);
    }
  };
}

function debounce(fn, wait, immediate) {
  if (!isFunction(fn)) {
    throw new Error('参数 fn 必须是函数类型');
  }
  if (!isNumber(wait)) {
    throw new Error('参数 wait 必须是数字类型');
  }
  if (immediate !== undefined && !isBoolean(immediate)) {
    throw new Error('参数 immediate 必须是布尔类型');
  }

  var timer;

  return function () {
    timer && clearTimeout(timer);
    var args = arguments;

    if (immediate) {
      !timer && fn.apply(this, args);
      // 在代码执行的wait s之后将timer设置为null
      // 当下一次执行当前函数则可以触发判断 !timer === true
      timer = setTimeout(function () {
        timer = null;
      }, wait);
    } else {
      timer = setTimeout(function () {
        fn.apply(this, args);
      }, wait);
    }
  };
}

// 合并对象
function mergeObj(target) {
  if (!target) {
    throw new Error('未传入目标对象 target');
  }
  Array.prototype.shift.call(arguments);
  Array.prototype.forEach.call(arguments, (obj) => {
    if (!isObject(obj)) {
      throw new Error('所有参数必须是对象类型');
    }
    Object.keys(obj).forEach(function (key) {
      target[key] = obj[key];
    });
  });
  return target;
}

// 二分搜索
function binarySearch(list, value) {
  if (!isArray(list)) {
    throw new Error('list 参数必须是数组类型');
  }
  if (!isNumber(value)) {
    throw new Error('value 参数必须是数组类型');
  }

  var left = 0;
  var right = list.length - 1;
  var tempIndex = null;
  while (left <= right) {
    var mid = Math.floor((left + right) / 2);
    var midValue = list[mid].bottom;
    if (midValue === value) {
      return mid + 1;
    } else if (midValue > value) {
      if (tempIndex === null || tempIndex > mid) {
        tempIndex = mid;
      }
      right--;
    } else if (midValue < value) {
      left = mid + 1;
    }
  }
  return tempIndex;
}

// 柯里化函数
function curry(callback, paraNum) {
  if (!_checkType('[object Function]', callback)) {
    throw new Error('callback 参数必须是函数类型');
  }

  if (!paraNum) paraNum = callback.length;
  var __curry = function (cb, num) {
    // 柯里逻辑函数
    var args = Array.prototype.slice.call(arguments);
    args.shift(); // 第一个参数出队
    args.shift(); // 第二个参数出队
    return function () {
      // 返回闭包函数
      var _args = Array.prototype.slice.call(arguments);
      var totalArgs = [];
      totalArgs.push.apply(totalArgs, args);
      totalArgs.push.apply(totalArgs, _args);
      if (totalArgs.length >= num) {
        return cb.apply(this, totalArgs);
      } else {
        totalArgs.unshift(cb, num);
        return __curry.apply(this, totalArgs);
      }
    };
  };
  return __curry(callback, paraNum);
}

function pagination(list, pageSize, pageNumber) {
  if (!isArray(list)) {
    throw new Error('list 参数必须是数组类型');
  }
  if (!isNumber(pageSize) || !isNumber(pageNumber)) {
    throw new Error('pageNumber、 pageSize 参数必须是数字类型');
  }

  return list.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
}

function findIndex(array, cb) {
  if (!isArray(array)) {
    throw new Error('array 参数必须是数组类型');
  }
  if (!isFunction(cb)) {
    throw new Error('cb 参数必须是函数类型');
  }

  var length = array.length;
  for (var i = 0; i < length; i++) {
    if (cb(array[i]) === undefined) return -1;
    if (cb(array[i])) return i;
  }
}

module.exports = {
  throttle: throttle,
  debounce: debounce,
  mergeObj: mergeObj,
  binarySearch: binarySearch,
  isString: isString,
  isArray: isArray,
  isFunction: isFunction,
  isObject: isObject,
  isNumber: isNumber,
  curry: curry,
  pagination: pagination,
  findIndex: findIndex,
};
